﻿
using System;
using System.Collections.Generic;
using System.Linq;
using System.Data;
using System.Data.Entity;
using System.Linq.Expressions;
using GTServices.Entities;
using System.Collections.ObjectModel;
using System.Windows.Threading;
using System.Windows;
using System.Threading;

namespace GTServices.Entities
{

    public enum RepositoryEventType
    {
          Added, 
          Updated, 
          Removed
    }

    public class Repository<TContext>
        where TContext : DbContext
    {
        TContext context;
        internal DbSet dbSet;

        public System.Collections.IList Local { get { return dbSet.Local; } }

        public Repository(TContext context, Type entityType)
        {
            this.context = context;
            this.dbSet = context.Set(entityType);
            this.dbSet.Load(); 
        }

        public BaseEntity Create()
        {
          BaseEntity be = (BaseEntity)this.dbSet.Create();
          return be; 
        }
        public bool Contains(BaseEntity entity)
        {
            foreach (BaseEntity be in this.dbSet.Local)
            {
                if (be == entity)
                {
                    return true;
                }
            }
            return false;
        }
        public virtual void Delete(BaseEntity entityToDelete)
        {
            context.Entry(entityToDelete).State = EntityState.Deleted;
            try
            {
                this.dbSet.Remove(entityToDelete);
            }
            catch { }

            this.Save();

            return;
        }


        public BaseEntity Add(BaseEntity type)
        {
            try
            {
                return (BaseEntity)this.dbSet.Add(type);
            }
            catch(Exception e)
            {
                //TODO Log
            }
            return null;
        }

        public void Save()
        {
            try
            {
                this.context.SaveChanges();
            }
            catch (Exception e)
            {
                //TODO: Log
            }
        }
    }

    public class Repository<TEntity, TContext> 
        where TEntity : BaseEntity
        where TContext : DbContext
    {
        public class RepositoryEventArguments : EventArgs
        {

            public RepositoryEventType EventType { get; set; }
            public Repository<TEntity, TContext> Repository { get; set; }
            public TEntity Entity { get; set; }
     
            public RepositoryEventArguments(TEntity entity, RepositoryEventType type, Repository<TEntity, TContext> resp)
            {
                this.EventType = type;
                this.Entity = entity;
                this.Repository = resp;
            }
        }

        public delegate void RepositoryEvent(RepositoryEventArguments args, object context = null);
        public event RepositoryEvent OnRepositoryEvent;


        TContext context;
        internal DbSet<TEntity> dbSet;
       
        public Repository(TContext context)
        {
            this.context = context;
            this.dbSet = context.Set<TEntity>();
            this.dbSet.Load();    
        }

        private void NotifyRepositoryEvent(IValidate entity, RepositoryEventType eventType, object context = null)
        {

            TEntity type = entity as TEntity;
            if (type != null)
            {
                var args = new RepositoryEventArguments(type, eventType, this);

                if (OnRepositoryEvent != null)
                {
                    OnRepositoryEvent(args, context);
                }
            }
        }

        public void Save()
        {
            this.context.SaveChanges();
        }

        public TEntity Create()
        {
            return this.dbSet.Create(); 
        }

        public ObservableCollection<TEntity> GetLocal()
        {

            return context.Set<TEntity>().Local; 
        }

        public virtual void Each( Action<TEntity> action){
            foreach (TEntity i in dbSet.ToList())
            {
                action(i);
            }
        }

        public virtual IEnumerable<TEntity> Get(
            Expression<Func<TEntity, bool>> filter = null,
            Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
            string includeProperties = "")
        {
         
            IQueryable<TEntity> query = dbSet;

            if (filter != null)
            {
                query = query.Where(filter);
            }

            foreach (var includeProperty in includeProperties.Split
                (new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
            {
                query = query.Include(includeProperty);
            }

            if (orderBy != null)
            {
                return orderBy(query).ToList();
            }
            else
            {
                return query.ToList();
            }
        }

        public virtual TEntity GetByID(object[] prams)
        {
            return dbSet.Find(prams);
        }

        public virtual TEntity Insert(TEntity entity, bool updateOnDup = false)
        {
            if (dbSet.Local.Contains(entity) && updateOnDup)
            {
                Update(entity);
                return entity;
            }
            else
            {
                TEntity result  = null; 
               
                // this.context.Entry(entity).State = EntityState.Added;
                result =  dbSet.Add(entity);
                
                if (result != null)
                {
                    NotifyRepositoryEvent(entity, RepositoryEventType.Added);
                    return result;
                }    
            }
            return null;
        }

        public virtual void Delete(object[] prams)
        {
            TEntity entityToDelete = dbSet.Find(prams);
            if (entityToDelete != null)
            {
                Delete(entityToDelete);
            }
            else
            {
                //TODO: Error COuld not find Entity. 
            }
        }

        public virtual void Delete(TEntity entityToDelete)
        {
            if (context.Entry(entityToDelete).State == EntityState.Detached)
            {
                dbSet.Attach(entityToDelete);
            }
            TEntity ent = dbSet.Remove(entityToDelete);
            if (ent != null)
            {
                NotifyRepositoryEvent(ent, RepositoryEventType.Removed);
            }
            else
            {
                //TDOO: Error?? Should never get here, but hey if we do, something's botched. 
            }
        }
       
        public virtual void Update(TEntity entityToUpdate)
        {
            TEntity entity = dbSet.Attach(entityToUpdate);
            if (entity != null)
            {
                context.Entry(entityToUpdate).State = EntityState.Modified;
                NotifyRepositoryEvent(entity, RepositoryEventType.Updated);
            }
        }
    }
}

